From d7be749335f2585658cf98c4f0e7d6cd5ac06865 Mon Sep 17 00:00:00 2001
From: Jouni Malinen <jouni@qca.qualcomm.com>
Date: Tue, 25 Jan 2022 00:35:36 +0200
Subject: [PATCH] DPP3: PKEX over TCP

Signed-off-by: Jouni Malinen <jouni@qca.qualcomm.com>

CVE: CVE-2022-37660

Upstream-Status: Backport [https://git.w1.fi/cgit/hostap/commit/?id=d7be749335f2585658cf98c4f0e7d6cd5ac06865]

Signed-off-by: Divya Chellam <divya.chellam@windriver.com>
---
 src/ap/dpp_hostapd.c            | 155 ++++++++++++++--
 src/common/dpp.h                |  13 ++
 src/common/dpp_pkex.c           |  18 +-
 src/common/dpp_tcp.c            | 308 +++++++++++++++++++++++++++++++-
 wpa_supplicant/dpp_supplicant.c | 122 ++++++++++++-
 5 files changed, 580 insertions(+), 36 deletions(-)

diff --git a/src/ap/dpp_hostapd.c b/src/ap/dpp_hostapd.c
index fdfdcf9..d956be9 100644
--- a/src/ap/dpp_hostapd.c
+++ b/src/ap/dpp_hostapd.c
@@ -28,12 +28,16 @@ static void hostapd_dpp_auth_conf_wait_timeout(void *eloop_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 void hostapd_dpp_set_testing_options(struct hostapd_data *hapd,
+					    struct dpp_authentication *auth);
 #ifdef CONFIG_DPP2
 static void hostapd_dpp_reconfig_reply_wait_timeout(void *eloop_ctx,
 						    void *timeout_ctx);
 static void hostapd_dpp_handle_config_obj(struct hostapd_data *hapd,
 					  struct dpp_authentication *auth,
 					  struct dpp_config_obj *conf);
+static int hostapd_dpp_process_conf_obj(void *ctx,
+					struct dpp_authentication *auth);
 #endif /* CONFIG_DPP2 */
 
 static const u8 broadcast[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
@@ -272,6 +276,75 @@ static int hostapd_dpp_pkex_next_channel(struct hostapd_data *hapd,
 }
 
 
+#ifdef CONFIG_DPP2
+static int hostapd_dpp_pkex_done(void *ctx, void *conn,
+				 struct dpp_bootstrap_info *peer_bi)
+{
+	struct hostapd_data *hapd = ctx;
+	const char *cmd = hapd->dpp_pkex_auth_cmd;
+	const char *pos;
+	u8 allowed_roles = DPP_CAPAB_CONFIGURATOR;
+	struct dpp_bootstrap_info *own_bi = NULL;
+	struct dpp_authentication *auth;
+
+	if (!cmd)
+		cmd = "";
+	wpa_printf(MSG_DEBUG, "DPP: Start authentication after PKEX (cmd: %s)",
+		   cmd);
+
+	pos = os_strstr(cmd, " own=");
+	if (pos) {
+		pos += 5;
+		own_bi = dpp_bootstrap_get_id(hapd->iface->interfaces->dpp,
+					      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
+			return -1;
+	}
+
+	auth = dpp_auth_init(hapd->iface->interfaces->dpp, hapd->msg_ctx,
+			     peer_bi, own_bi, allowed_roles, 0,
+			     hapd->iface->hw_features,
+			     hapd->iface->num_hw_features);
+	if (!auth)
+		return -1;
+
+	hostapd_dpp_set_testing_options(hapd, auth);
+	if (dpp_set_configurator(auth, cmd) < 0) {
+		dpp_auth_deinit(auth);
+		return -1;
+	}
+
+	return dpp_tcp_auth(hapd->iface->interfaces->dpp, conn, auth,
+			    hapd->conf->dpp_name, DPP_NETROLE_AP,
+			    hostapd_dpp_process_conf_obj);
+}
+#endif /* CONFIG_DPP2 */
+
+
 enum hostapd_dpp_pkex_ver {
 	PKEX_VER_AUTO,
 	PKEX_VER_ONLY_1,
@@ -279,7 +352,9 @@ enum hostapd_dpp_pkex_ver {
 };
 
 static int hostapd_dpp_pkex_init(struct hostapd_data *hapd,
-				 enum hostapd_dpp_pkex_ver ver)
+				 enum hostapd_dpp_pkex_ver ver,
+				 const struct hostapd_ip_addr *ipaddr,
+				 int tcp_port)
 {
 	struct dpp_pkex *pkex;
 	struct wpabuf *msg;
@@ -288,15 +363,26 @@ static int hostapd_dpp_pkex_init(struct hostapd_data *hapd,
 
 	wpa_printf(MSG_DEBUG, "DPP: Initiating PKEXv%d", v2 ? 2 : 1);
 	dpp_pkex_free(hapd->dpp_pkex);
-	hapd->dpp_pkex = dpp_pkex_init(hapd->msg_ctx, hapd->dpp_pkex_bi,
-				       hapd->own_addr,
-				       hapd->dpp_pkex_identifier,
-				       hapd->dpp_pkex_code, v2);
-	pkex = hapd->dpp_pkex;
+	hapd->dpp_pkex = NULL;
+	pkex = dpp_pkex_init(hapd->msg_ctx, hapd->dpp_pkex_bi, hapd->own_addr,
+			     hapd->dpp_pkex_identifier,
+			     hapd->dpp_pkex_code, v2);
 	if (!pkex)
 		return -1;
 	pkex->forced_ver = ver != PKEX_VER_AUTO;
 
+	if (ipaddr) {
+#ifdef CONFIG_DPP2
+		return dpp_tcp_pkex_init(hapd->iface->interfaces->dpp, pkex,
+					 ipaddr, tcp_port,
+					 hapd->msg_ctx, hapd,
+					 hostapd_dpp_pkex_done);
+#else /* CONFIG_DPP2 */
+		return -1;
+#endif /* CONFIG_DPP2 */
+	}
+
+	hapd->dpp_pkex = pkex;
 	msg = hapd->dpp_pkex->exchange_req;
 	wait_time = 2000; /* TODO: hapd->max_remain_on_chan; */
 	pkex->freq = 2437;
@@ -326,7 +412,8 @@ static void hostapd_dpp_pkex_retry_timeout(void *eloop_ctx, void *timeout_ctx)
 			if (pkex->v2 && !pkex->forced_ver) {
 				wpa_printf(MSG_DEBUG,
 					   "DPP: Fall back to PKEXv1");
-				hostapd_dpp_pkex_init(hapd, PKEX_VER_ONLY_1);
+				hostapd_dpp_pkex_init(hapd, PKEX_VER_ONLY_1,
+						      NULL, 0);
 				return;
 			}
 #endif /* CONFIG_DPP3 */
@@ -1883,7 +1970,7 @@ static void hostapd_dpp_rx_peer_disc_req(struct hostapd_data *hapd,
 
 static void
 hostapd_dpp_rx_pkex_exchange_req(struct hostapd_data *hapd, const u8 *src,
-				 const u8 *buf, size_t len,
+				 const u8 *hdr, const u8 *buf, size_t len,
 				 unsigned int freq, bool v2)
 {
 	struct wpabuf *msg;
@@ -1897,14 +1984,14 @@ hostapd_dpp_rx_pkex_exchange_req(struct hostapd_data *hapd, const u8 *src,
 	if (!hapd->dpp_pkex_code || !hapd->dpp_pkex_bi) {
 		wpa_printf(MSG_DEBUG,
 			   "DPP: No PKEX code configured - ignore request");
-		return;
+		goto try_relay;
 	}
 
 	if (hapd->dpp_pkex) {
 		/* TODO: Support parallel operations */
 		wpa_printf(MSG_DEBUG,
 			   "DPP: Already in PKEX session - ignore new request");
-		return;
+		goto try_relay;
 	}
 
 	hapd->dpp_pkex = dpp_pkex_rx_exchange_req(hapd->msg_ctx,
@@ -1916,7 +2003,7 @@ hostapd_dpp_rx_pkex_exchange_req(struct hostapd_data *hapd, const u8 *src,
 	if (!hapd->dpp_pkex) {
 		wpa_printf(MSG_DEBUG,
 			   "DPP: Failed to process the request - ignore it");
-		return;
+		goto try_relay;
 	}
 
 	msg = hapd->dpp_pkex->exchange_resp;
@@ -1933,6 +2020,17 @@ hostapd_dpp_rx_pkex_exchange_req(struct hostapd_data *hapd, const u8 *src,
 		dpp_pkex_free(hapd->dpp_pkex);
 		hapd->dpp_pkex = NULL;
 	}
+
+	return;
+
+try_relay:
+#ifdef CONFIG_DPP2
+	if (v2)
+		dpp_relay_rx_action(hapd->iface->interfaces->dpp,
+				    src, hdr, buf, len, freq, NULL, NULL, hapd);
+#else /* CONFIG_DPP2 */
+	wpa_printf(MSG_DEBUG, "DPP: No relay functionality included - skip");
+#endif /* CONFIG_DPP2 */
 }
 
 
@@ -2132,12 +2230,12 @@ void hostapd_dpp_rx_action(struct hostapd_data *hapd, const u8 *src,
 		/* This is for PKEXv2, but for now, process only with
 		 * CONFIG_DPP3 to avoid issues with a capability that has not
 		 * been tested with other implementations. */
-		hostapd_dpp_rx_pkex_exchange_req(hapd, src, buf, len, freq,
+		hostapd_dpp_rx_pkex_exchange_req(hapd, src, hdr, buf, len, freq,
 						 true);
 		break;
 #endif /* CONFIG_DPP3 */
 	case DPP_PA_PKEX_V1_EXCHANGE_REQ:
-		hostapd_dpp_rx_pkex_exchange_req(hapd, src, buf, len, freq,
+		hostapd_dpp_rx_pkex_exchange_req(hapd, src, hdr, buf, len, freq,
 						 false);
 		break;
 	case DPP_PA_PKEX_EXCHANGE_RESP:
@@ -2303,6 +2401,29 @@ int hostapd_dpp_pkex_add(struct hostapd_data *hapd, const char *cmd)
 {
 	struct dpp_bootstrap_info *own_bi;
 	const char *pos, *end;
+	int tcp_port = DPP_TCP_PORT;
+	struct hostapd_ip_addr *ipaddr = NULL;
+#ifdef CONFIG_DPP2
+	struct hostapd_ip_addr ipaddr_buf;
+	char *addr;
+
+	pos = os_strstr(cmd, " tcp_port=");
+	if (pos) {
+		pos += 10;
+		tcp_port = atoi(pos);
+	}
+
+	addr = get_param(cmd, " tcp_addr=");
+	if (addr) {
+		int res;
+
+		res = hostapd_parse_ip_addr(addr, &ipaddr_buf);
+		os_free(addr);
+		if (res)
+			return -1;
+		ipaddr = &ipaddr_buf;
+	}
+#endif /* CONFIG_DPP2 */
 
 	pos = os_strstr(cmd, " own=");
 	if (!pos)
@@ -2366,8 +2487,14 @@ int hostapd_dpp_pkex_add(struct hostapd_data *hapd, const char *cmd)
 				return -1;
 		}
 
-		if (hostapd_dpp_pkex_init(hapd, ver) < 0)
+		if (hostapd_dpp_pkex_init(hapd, ver, ipaddr, tcp_port) < 0)
 			return -1;
+	} else {
+#ifdef CONFIG_DPP2
+		dpp_controller_pkex_add(hapd->iface->interfaces->dpp, own_bi,
+					hapd->dpp_pkex_code,
+					hapd->dpp_pkex_identifier);
+#endif /* CONFIG_DPP2 */
 	}
 
 	/* TODO: Support multiple PKEX info entries */
diff --git a/src/common/dpp.h b/src/common/dpp.h
index bfea446..ca33fe3 100644
--- a/src/common/dpp.h
+++ b/src/common/dpp.h
@@ -550,6 +550,9 @@ 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_controller_pkex_add(struct dpp_global *dpp,
+			     struct dpp_bootstrap_info *bi,
+			     const char *code, const char *identifier);
 struct dpp_configuration * dpp_configuration_alloc(const char *type);
 int dpp_akm_psk(enum dpp_akm akm);
 int dpp_akm_sae(enum dpp_akm akm);
@@ -688,12 +691,22 @@ struct dpp_authentication * dpp_controller_get_auth(struct dpp_global *dpp,
 						    unsigned int id);
 void dpp_controller_new_qr_code(struct dpp_global *dpp,
 				struct dpp_bootstrap_info *bi);
+int dpp_tcp_pkex_init(struct dpp_global *dpp, struct dpp_pkex *pkex,
+		      const struct hostapd_ip_addr *addr, int port,
+		      void *msg_ctx, void *cb_ctx,
+		      int (*pkex_done)(void *ctx, void *conn,
+				       struct dpp_bootstrap_info *bi));
 int dpp_tcp_init(struct dpp_global *dpp, struct dpp_authentication *auth,
 		 const struct hostapd_ip_addr *addr, int port,
 		 const char *name, enum dpp_netrole netrole, void *msg_ctx,
 		 void *cb_ctx,
 		 int (*process_conf_obj)(void *ctx,
 					 struct dpp_authentication *auth));
+int dpp_tcp_auth(struct dpp_global *dpp, void *_conn,
+		 struct dpp_authentication *auth, const char *name,
+		 enum dpp_netrole netrole,
+		 int (*process_conf_obj)(void *ctx,
+					 struct dpp_authentication *auth));
 
 struct wpabuf * dpp_build_presence_announcement(struct dpp_bootstrap_info *bi);
 void dpp_notify_chirp_received(void *msg_ctx, int id, const u8 *src,
diff --git a/src/common/dpp_pkex.c b/src/common/dpp_pkex.c
index 38349fa..72084d9 100644
--- a/src/common/dpp_pkex.c
+++ b/src/common/dpp_pkex.c
@@ -469,8 +469,10 @@ struct dpp_pkex * dpp_pkex_rx_exchange_req(void *msg_ctx,
 	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 (own_mac)
+		os_memcpy(pkex->own_mac, own_mac, ETH_ALEN);
+	if (peer_mac)
+		os_memcpy(pkex->peer_mac, peer_mac, ETH_ALEN);
 	if (identifier) {
 		pkex->identifier = os_strdup(identifier);
 		if (!pkex->identifier)
@@ -742,7 +744,8 @@ struct wpabuf * dpp_pkex_rx_exchange_resp(struct dpp_pkex *pkex,
 	}
 #endif /* CONFIG_DPP2 */
 
-	os_memcpy(pkex->peer_mac, peer_mac, ETH_ALEN);
+	if (peer_mac)
+		os_memcpy(pkex->peer_mac, peer_mac, ETH_ALEN);
 
 	attr_status = dpp_get_attr(buf, buflen, DPP_ATTR_STATUS,
 				   &attr_status_len);
@@ -1341,9 +1344,12 @@ dpp_pkex_finish(struct dpp_global *dpp, struct dpp_pkex *pkex, const u8 *peer,
 		return NULL;
 	bi->id = dpp_next_id(dpp);
 	bi->type = DPP_BOOTSTRAP_PKEX;
-	os_memcpy(bi->mac_addr, peer, ETH_ALEN);
-	bi->num_freq = 1;
-	bi->freq[0] = freq;
+	if (peer)
+		os_memcpy(bi->mac_addr, peer, ETH_ALEN);
+	if (freq) {
+		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;
diff --git a/src/common/dpp_tcp.c b/src/common/dpp_tcp.c
index fb8ef1c..1a8a7c7 100644
--- a/src/common/dpp_tcp.c
+++ b/src/common/dpp_tcp.c
@@ -24,10 +24,12 @@ struct dpp_connection {
 	struct dpp_controller *ctrl;
 	struct dpp_relay_controller *relay;
 	struct dpp_global *global;
+	struct dpp_pkex *pkex;
 	struct dpp_authentication *auth;
 	void *msg_ctx;
 	void *cb_ctx;
 	int (*process_conf_obj)(void *ctx, struct dpp_authentication *auth);
+	int (*pkex_done)(void *ctx, void *conn, struct dpp_bootstrap_info *bi);
 	int sock;
 	u8 mac_addr[ETH_ALEN];
 	unsigned int freq;
@@ -71,6 +73,9 @@ struct dpp_controller {
 	struct dl_list conn; /* struct dpp_connection */
 	char *configurator_params;
 	enum dpp_netrole netrole;
+	struct dpp_bootstrap_info *pkex_bi;
+	char *pkex_code;
+	char *pkex_identifier;
 	void *msg_ctx;
 	void *cb_ctx;
 	int (*process_conf_obj)(void *ctx, struct dpp_authentication *auth);
@@ -102,6 +107,7 @@ static void dpp_connection_free(struct dpp_connection *conn)
 	wpabuf_free(conn->msg);
 	wpabuf_free(conn->msg_out);
 	dpp_auth_deinit(conn->auth);
+	dpp_pkex_free(conn->pkex);
 	os_free(conn->name);
 	os_free(conn);
 }
@@ -525,6 +531,8 @@ int dpp_relay_rx_action(struct dpp_global *dpp, const u8 *src, const u8 *hdr,
 		/* TODO: Could send this to all configured Controllers. For now,
 		 * only the first Controller is supported. */
 		ctrl = dpp_relay_controller_get_ctx(dpp, cb_ctx);
+	} else if (type == DPP_PA_PKEX_EXCHANGE_REQ) {
+		ctrl = dpp_relay_controller_get_ctx(dpp, cb_ctx);
 	} else {
 		if (!r_bootstrap)
 			return -1;
@@ -609,6 +617,8 @@ static void dpp_controller_free(struct dpp_controller *ctrl)
 		eloop_unregister_sock(ctrl->sock, EVENT_TYPE_READ);
 	}
 	os_free(ctrl->configurator_params);
+	os_free(ctrl->pkex_code);
+	os_free(ctrl->pkex_identifier);
 	os_free(ctrl);
 }
 
@@ -955,6 +965,143 @@ static int dpp_controller_rx_reconfig_auth_resp(struct dpp_connection *conn,
 }
 
 
+static int dpp_controller_rx_pkex_exchange_req(struct dpp_connection *conn,
+					       const u8 *hdr, const u8 *buf,
+					       size_t len)
+{
+	struct dpp_controller *ctrl = conn->ctrl;
+
+	if (!ctrl)
+		return 0;
+
+	wpa_printf(MSG_DEBUG, "DPP: PKEX Exchange Request");
+
+	/* TODO: Support multiple PKEX codes by iterating over all the enabled
+	 * values here */
+
+	if (!ctrl->pkex_code || !ctrl->pkex_bi) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: No PKEX code configured - ignore request");
+		return 0;
+	}
+
+	if (conn->pkex || conn->auth) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Already in PKEX/Authentication session - ignore new PKEX request");
+		return 0;
+	}
+
+	conn->pkex = dpp_pkex_rx_exchange_req(conn->ctrl->global, ctrl->pkex_bi,
+					      NULL, NULL,
+					      ctrl->pkex_identifier,
+					      ctrl->pkex_code,
+					      buf, len, true);
+	if (!conn->pkex) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Failed to process the request");
+		return -1;
+	}
+
+	return dpp_tcp_send_msg(conn, conn->pkex->exchange_resp);
+}
+
+
+static int dpp_controller_rx_pkex_exchange_resp(struct dpp_connection *conn,
+						const u8 *hdr, const u8 *buf,
+						size_t len)
+{
+	struct dpp_pkex *pkex = conn->pkex;
+	struct wpabuf *msg;
+	int res;
+
+	wpa_printf(MSG_DEBUG, "DPP: PKEX Exchange Response");
+
+	if (!pkex || !pkex->initiator || pkex->exchange_done) {
+		wpa_printf(MSG_DEBUG, "DPP: No matching PKEX session");
+		return 0;
+	}
+
+	msg = dpp_pkex_rx_exchange_resp(pkex, NULL, buf, len);
+	if (!msg) {
+		wpa_printf(MSG_DEBUG, "DPP: Failed to process the response");
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "DPP: Send PKEX Commit-Reveal Request");
+	res = dpp_tcp_send_msg(conn, msg);
+	wpabuf_free(msg);
+	return res;
+}
+
+
+static int dpp_controller_rx_pkex_commit_reveal_req(struct dpp_connection *conn,
+						    const u8 *hdr,
+						    const u8 *buf, size_t len)
+{
+	struct dpp_pkex *pkex = conn->pkex;
+	struct wpabuf *msg;
+	int res;
+	struct dpp_bootstrap_info *bi;
+
+	wpa_printf(MSG_DEBUG, "DPP: PKEX Commit-Reveal Request");
+
+	if (!pkex || pkex->initiator || !pkex->exchange_done) {
+		wpa_printf(MSG_DEBUG, "DPP: No matching PKEX session");
+		return 0;
+	}
+
+	msg = dpp_pkex_rx_commit_reveal_req(pkex, hdr, buf, len);
+	if (!msg) {
+		wpa_printf(MSG_DEBUG, "DPP: Failed to process the request");
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "DPP: Send PKEX Commit-Reveal Response");
+	res = dpp_tcp_send_msg(conn, msg);
+	wpabuf_free(msg);
+	if (res < 0)
+		return res;
+	bi = dpp_pkex_finish(conn->global, pkex, NULL, 0);
+	if (!bi)
+		return -1;
+	conn->pkex = NULL;
+	return 0;
+}
+
+
+static int
+dpp_controller_rx_pkex_commit_reveal_resp(struct dpp_connection *conn,
+					  const u8 *hdr,
+					  const u8 *buf, size_t len)
+{
+	struct dpp_pkex *pkex = conn->pkex;
+	int res;
+	struct dpp_bootstrap_info *bi;
+
+	wpa_printf(MSG_DEBUG, "DPP: PKEX Commit-Reveal Response");
+
+	if (!pkex || !pkex->initiator || !pkex->exchange_done) {
+		wpa_printf(MSG_DEBUG, "DPP: No matching PKEX session");
+		return 0;
+	}
+
+	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 res;
+	}
+
+	bi = dpp_pkex_finish(conn->global, pkex, NULL, 0);
+	if (!bi)
+		return -1;
+	conn->pkex = NULL;
+
+	if (!conn->pkex_done)
+		return -1;
+	return conn->pkex_done(conn->cb_ctx, conn, bi);
+}
+
+
 static int dpp_controller_rx_action(struct dpp_connection *conn, const u8 *msg,
 				    size_t len)
 {
@@ -1014,6 +1161,22 @@ static int dpp_controller_rx_action(struct dpp_connection *conn, const u8 *msg,
 	case DPP_PA_RECONFIG_AUTH_RESP:
 		return dpp_controller_rx_reconfig_auth_resp(conn, msg, pos,
 							    end - pos);
+	case DPP_PA_PKEX_V1_EXCHANGE_REQ:
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Ignore PKEXv1 Exchange Request - not supported over TCP");
+		return -1;
+	case DPP_PA_PKEX_EXCHANGE_REQ:
+		return dpp_controller_rx_pkex_exchange_req(conn, msg, pos,
+							   end - pos);
+	case DPP_PA_PKEX_EXCHANGE_RESP:
+		return dpp_controller_rx_pkex_exchange_resp(conn, msg, pos,
+							    end - pos);
+	case DPP_PA_PKEX_COMMIT_REVEAL_REQ:
+		return dpp_controller_rx_pkex_commit_reveal_req(conn, msg, pos,
+								end - pos);
+	case DPP_PA_PKEX_COMMIT_REVEAL_RESP:
+		return dpp_controller_rx_pkex_commit_reveal_resp(conn, msg, pos,
+								 end - pos);
 	default:
 		/* TODO: missing messages types */
 		wpa_printf(MSG_DEBUG,
@@ -1559,6 +1722,101 @@ fail:
 }
 
 
+int dpp_tcp_pkex_init(struct dpp_global *dpp, struct dpp_pkex *pkex,
+		      const struct hostapd_ip_addr *addr, int port,
+		      void *msg_ctx, void *cb_ctx,
+		      int (*pkex_done)(void *ctx, void *conn,
+				       struct dpp_bootstrap_info *bi))
+{
+	struct dpp_connection *conn;
+	struct sockaddr_storage saddr;
+	socklen_t addrlen;
+	const u8 *hdr, *pos, *end;
+	char txt[100];
+
+	wpa_printf(MSG_DEBUG, "DPP: Initialize TCP connection to %s port %d",
+		   hostapd_ip_txt(addr, txt, sizeof(txt)), port);
+	if (dpp_ipaddr_to_sockaddr((struct sockaddr *) &saddr, &addrlen,
+				   addr, port) < 0) {
+		dpp_pkex_free(pkex);
+		return -1;
+	}
+
+	conn = os_zalloc(sizeof(*conn));
+	if (!conn) {
+		dpp_pkex_free(pkex);
+		return -1;
+	}
+
+	conn->msg_ctx = msg_ctx;
+	conn->cb_ctx = cb_ctx;
+	conn->pkex_done = pkex_done;
+	conn->global = dpp;
+	conn->pkex = pkex;
+	conn->sock = socket(AF_INET, SOCK_STREAM, 0);
+	if (conn->sock < 0)
+		goto fail;
+
+	if (fcntl(conn->sock, F_SETFL, O_NONBLOCK) != 0) {
+		wpa_printf(MSG_DEBUG, "DPP: fnctl(O_NONBLOCK) failed: %s",
+			   strerror(errno));
+		goto fail;
+	}
+
+	if (connect(conn->sock, (struct sockaddr *) &saddr, addrlen) < 0) {
+		if (errno != EINPROGRESS) {
+			wpa_printf(MSG_DEBUG, "DPP: Failed to connect: %s",
+				   strerror(errno));
+			goto fail;
+		}
+
+		/*
+		 * Continue connecting in the background; eloop will call us
+		 * once the connection is ready (or failed).
+		 */
+	}
+
+	if (eloop_register_sock(conn->sock, EVENT_TYPE_WRITE,
+				dpp_conn_tx_ready, conn, NULL) < 0)
+		goto fail;
+	conn->write_eloop = 1;
+
+	hdr = wpabuf_head(pkex->exchange_req);
+	end = hdr + wpabuf_len(pkex->exchange_req);
+	hdr += 2; /* skip Category and Actiom */
+	pos = hdr + DPP_HDR_LEN;
+	conn->msg_out = dpp_tcp_encaps(hdr, pos, end - pos);
+	if (!conn->msg_out)
+		goto fail;
+	/* Message will be sent in dpp_conn_tx_ready() */
+
+	/* TODO: eloop timeout to clear a connection if it does not complete
+	 * properly */
+	dl_list_add(&dpp->tcp_init, &conn->list);
+	return 0;
+fail:
+	dpp_connection_free(conn);
+	return -1;
+}
+
+
+static int dpp_tcp_auth_start(struct dpp_connection *conn,
+			      struct dpp_authentication *auth)
+{
+	const u8 *hdr, *pos, *end;
+
+	hdr = wpabuf_head(auth->req_msg);
+	end = hdr + wpabuf_len(auth->req_msg);
+	hdr += 2; /* skip Category and Actiom */
+	pos = hdr + DPP_HDR_LEN;
+	conn->msg_out = dpp_tcp_encaps(hdr, pos, end - pos);
+	if (!conn->msg_out)
+		return -1;
+	/* Message will be sent in dpp_conn_tx_ready() */
+	return 0;
+}
+
+
 int dpp_tcp_init(struct dpp_global *dpp, struct dpp_authentication *auth,
 		 const struct hostapd_ip_addr *addr, int port, const char *name,
 		 enum dpp_netrole netrole, void *msg_ctx, void *cb_ctx,
@@ -1568,7 +1826,6 @@ int dpp_tcp_init(struct dpp_global *dpp, struct dpp_authentication *auth,
 	struct dpp_connection *conn;
 	struct sockaddr_storage saddr;
 	socklen_t addrlen;
-	const u8 *hdr, *pos, *end;
 	char txt[100];
 
 	wpa_printf(MSG_DEBUG, "DPP: Initialize TCP connection to %s port %d",
@@ -1620,14 +1877,8 @@ int dpp_tcp_init(struct dpp_global *dpp, struct dpp_authentication *auth,
 		goto fail;
 	conn->write_eloop = 1;
 
-	hdr = wpabuf_head(auth->req_msg);
-	end = hdr + wpabuf_len(auth->req_msg);
-	hdr += 2; /* skip Category and Actiom */
-	pos = hdr + DPP_HDR_LEN;
-	conn->msg_out = dpp_tcp_encaps(hdr, pos, end - pos);
-	if (!conn->msg_out)
+	if (dpp_tcp_auth_start(conn, auth) < 0)
 		goto fail;
-	/* Message will be sent in dpp_conn_tx_ready() */
 
 	/* TODO: eloop timeout to clear a connection if it does not complete
 	 * properly */
@@ -1639,6 +1890,30 @@ fail:
 }
 
 
+int dpp_tcp_auth(struct dpp_global *dpp, void *_conn,
+		 struct dpp_authentication *auth, const char *name,
+		 enum dpp_netrole netrole,
+		 int (*process_conf_obj)(void *ctx,
+					 struct dpp_authentication *auth))
+{
+	struct dpp_connection *conn = _conn;
+
+	/* Continue with Authentication exchange on an existing TCP connection.
+	 */
+	conn->process_conf_obj = process_conf_obj;
+	os_free(conn->name);
+	conn->name = os_strdup(name ? name : "Test");
+	conn->netrole = netrole;
+	conn->auth = auth;
+
+	if (dpp_tcp_auth_start(conn, auth) < 0)
+		return -1;
+
+	dpp_conn_tx_ready(conn->sock, conn, NULL);
+	return 0;
+}
+
+
 int dpp_controller_start(struct dpp_global *dpp,
 			 struct dpp_controller_config *config)
 {
@@ -1789,6 +2064,23 @@ void dpp_controller_new_qr_code(struct dpp_global *dpp,
 }
 
 
+void dpp_controller_pkex_add(struct dpp_global *dpp,
+			     struct dpp_bootstrap_info *bi,
+			     const char *code, const char *identifier)
+{
+	struct dpp_controller *ctrl = dpp->controller;
+
+	if (!ctrl)
+		return;
+
+	ctrl->pkex_bi = bi;
+	os_free(ctrl->pkex_code);
+	ctrl->pkex_code = code ? os_strdup(code) : NULL;
+	os_free(ctrl->pkex_identifier);
+	ctrl->pkex_identifier = identifier ? os_strdup(identifier) : NULL;
+}
+
+
 void dpp_tcp_init_flush(struct dpp_global *dpp)
 {
 	struct dpp_connection *conn, *tmp;
diff --git a/wpa_supplicant/dpp_supplicant.c b/wpa_supplicant/dpp_supplicant.c
index 61b300f..aab94cb 100644
--- a/wpa_supplicant/dpp_supplicant.c
+++ b/wpa_supplicant/dpp_supplicant.c
@@ -2557,6 +2557,71 @@ static int wpas_dpp_pkex_next_channel(struct wpa_supplicant *wpa_s,
 }
 
 
+#ifdef CONFIG_DPP2
+static int wpas_dpp_pkex_done(void *ctx, void *conn,
+			      struct dpp_bootstrap_info *peer_bi)
+{
+	struct wpa_supplicant *wpa_s = ctx;
+	const char *cmd = wpa_s->dpp_pkex_auth_cmd;
+	const char *pos;
+	u8 allowed_roles = DPP_CAPAB_CONFIGURATOR;
+	struct dpp_bootstrap_info *own_bi = NULL;
+	struct dpp_authentication *auth;
+
+	if (!cmd)
+		cmd = "";
+	wpa_printf(MSG_DEBUG, "DPP: Start authentication after PKEX (cmd: %s)",
+		   cmd);
+
+	pos = os_strstr(cmd, " own=");
+	if (pos) {
+		pos += 5;
+		own_bi = dpp_bootstrap_get_id(wpa_s->dpp, 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
+			return -1;
+	}
+
+	auth = dpp_auth_init(wpa_s->dpp, wpa_s, peer_bi, own_bi, allowed_roles,
+			     0, wpa_s->hw.modes, wpa_s->hw.num_modes);
+	if (!auth)
+		return -1;
+
+	wpas_dpp_set_testing_options(wpa_s, auth);
+	if (dpp_set_configurator(auth, cmd) < 0) {
+		dpp_auth_deinit(auth);
+		return -1;
+	}
+
+	return dpp_tcp_auth(wpa_s->dpp, conn, auth, wpa_s->conf->dpp_name,
+			    DPP_NETROLE_STA, wpas_dpp_process_conf_obj);
+}
+#endif /* CONFIG_DPP2 */
+
+
 enum wpas_dpp_pkex_ver {
 	PKEX_VER_AUTO,
 	PKEX_VER_ONLY_1,
@@ -2564,7 +2629,9 @@ enum wpas_dpp_pkex_ver {
 };
 
 static int wpas_dpp_pkex_init(struct wpa_supplicant *wpa_s,
-			      enum wpas_dpp_pkex_ver ver)
+			      enum wpas_dpp_pkex_ver ver,
+			      const struct hostapd_ip_addr *ipaddr,
+			      int tcp_port)
 {
 	struct dpp_pkex *pkex;
 	struct wpabuf *msg;
@@ -2573,15 +2640,24 @@ static int wpas_dpp_pkex_init(struct wpa_supplicant *wpa_s,
 
 	wpa_printf(MSG_DEBUG, "DPP: Initiating PKEXv%d", v2 ? 2 : 1);
 	dpp_pkex_free(wpa_s->dpp_pkex);
-	wpa_s->dpp_pkex = dpp_pkex_init(wpa_s, wpa_s->dpp_pkex_bi,
-					wpa_s->own_addr,
-					wpa_s->dpp_pkex_identifier,
-					wpa_s->dpp_pkex_code, v2);
-	pkex = wpa_s->dpp_pkex;
+	wpa_s->dpp_pkex = NULL;
+	pkex = dpp_pkex_init(wpa_s, wpa_s->dpp_pkex_bi, wpa_s->own_addr,
+			     wpa_s->dpp_pkex_identifier,
+			     wpa_s->dpp_pkex_code, v2);
 	if (!pkex)
 		return -1;
 	pkex->forced_ver = ver != PKEX_VER_AUTO;
 
+	if (ipaddr) {
+#ifdef CONFIG_DPP2
+		return dpp_tcp_pkex_init(wpa_s->dpp, pkex, ipaddr, tcp_port,
+					 wpa_s, wpa_s, wpas_dpp_pkex_done);
+#else /* CONFIG_DPP2 */
+		return -1;
+#endif /* CONFIG_DPP2 */
+	}
+
+	wpa_s->dpp_pkex = pkex;
 	msg = pkex->exchange_req;
 	wait_time = wpa_s->max_remain_on_chan;
 	if (wait_time > 2000)
@@ -2618,7 +2694,8 @@ static void wpas_dpp_pkex_retry_timeout(void *eloop_ctx, void *timeout_ctx)
 			if (pkex->v2 && !pkex->forced_ver) {
 				wpa_printf(MSG_DEBUG,
 					   "DPP: Fall back to PKEXv1");
-				wpas_dpp_pkex_init(wpa_s, PKEX_VER_ONLY_1);
+				wpas_dpp_pkex_init(wpa_s, PKEX_VER_ONLY_1,
+						   NULL, 0);
 				return;
 			}
 #endif /* CONFIG_DPP3 */
@@ -3327,6 +3404,29 @@ int wpas_dpp_pkex_add(struct wpa_supplicant *wpa_s, const char *cmd)
 {
 	struct dpp_bootstrap_info *own_bi;
 	const char *pos, *end;
+	int tcp_port = DPP_TCP_PORT;
+	struct hostapd_ip_addr *ipaddr = NULL;
+#ifdef CONFIG_DPP2
+	struct hostapd_ip_addr ipaddr_buf;
+	char *addr;
+
+	pos = os_strstr(cmd, " tcp_port=");
+	if (pos) {
+		pos += 10;
+		tcp_port = atoi(pos);
+	}
+
+	addr = get_param(cmd, " tcp_addr=");
+	if (addr) {
+		int res;
+
+		res = hostapd_parse_ip_addr(addr, &ipaddr_buf);
+		os_free(addr);
+		if (res)
+			return -1;
+		ipaddr = &ipaddr_buf;
+	}
+#endif /* CONFIG_DPP2 */
 
 	pos = os_strstr(cmd, " own=");
 	if (!pos)
@@ -3390,8 +3490,14 @@ int wpas_dpp_pkex_add(struct wpa_supplicant *wpa_s, const char *cmd)
 				return -1;
 		}
 
-		if (wpas_dpp_pkex_init(wpa_s, ver) < 0)
+		if (wpas_dpp_pkex_init(wpa_s, ver, ipaddr, tcp_port) < 0)
 			return -1;
+	} else {
+#ifdef CONFIG_DPP2
+		dpp_controller_pkex_add(wpa_s->dpp, own_bi,
+					wpa_s->dpp_pkex_code,
+					wpa_s->dpp_pkex_identifier);
+#endif /* CONFIG_DPP2 */
 	}
 
 	/* TODO: Support multiple PKEX info entries */
-- 
2.40.0

