User override of server certificate

Store server certificate hash and TOD policy from WPA3-Enterprise
connection attempts with sta_associate. Add support for dev_exec_action
ServerCertTrust,Accept argument. If TOD policy is in place for the
current network profile, reject that command; if TOD policy is not in
place, update network profile to use the last received server
certificate (from the last failed connection attempt) and try to
reconnect.

Signed-off-by: Jouni Malinen <jouni@codeaurora.org>
diff --git a/dev.c b/dev.c
index ada3f90..e70b7fa 100644
--- a/dev.c
+++ b/dev.c
@@ -67,11 +67,129 @@
 }
 
 
+static enum sigma_cmd_result sta_server_cert_trust(struct sigma_dut *dut,
+						   struct sigma_conn *conn,
+						   const char *val)
+{
+	char buf[100];
+	struct wpa_ctrl *ctrl = NULL;
+	int e;
+	char resp[200];
+	int num_disconnected = 0;
+
+	strlcpy(resp, "ServerCertTrustResult,Accepted", sizeof(resp));
+
+	if (strcasecmp(val, "Accept") != 0) {
+		strlcpy(resp,
+			"ServerCertTrustResult,NotAccepted,Reason,Unsupported ServerCertTrust value",
+			sizeof(resp));
+		goto done;
+	}
+
+	if (!dut->server_cert_hash[0]) {
+		strlcpy(resp,
+			"ServerCertTrustResult,NotAccepted,Reason,No server certificate stored",
+			sizeof(resp));
+		goto done;
+	}
+
+	if (dut->sta_tod_policy) {
+		strlcpy(resp,
+			"ServerCertTrustResult,NotAccepted,Reason,TOD policy",
+			sizeof(resp));
+		goto done;
+	}
+
+	snprintf(buf, sizeof(buf), "hash://server/sha256/%s",
+		 dut->server_cert_hash);
+	if (set_network_quoted(get_station_ifname(), dut->infra_network_id,
+			       "ca_cert", buf) < 0) {
+		strlcpy(resp,
+			"ServerCertTrustResult,NotAccepted,Reason,Could not configure server certificate hash for the network profile",
+			sizeof(resp));
+		goto done;
+	}
+
+	wpa_command(get_station_ifname(), "DISCONNECT");
+	snprintf(buf, sizeof(buf), "SELECT_NETWORK %d", dut->infra_network_id);
+	if (wpa_command(get_station_ifname(), buf) < 0) {
+		sigma_dut_print(dut, DUT_MSG_INFO, "Failed to select "
+				"network id %d on %s",
+				dut->infra_network_id,
+				get_station_ifname());
+		strlcpy(resp,
+			"ServerCertTrustResult,Accepted,Result,Could not request reconnection",
+			sizeof(resp));
+		goto done;
+	}
+
+	ctrl = open_wpa_mon(get_station_ifname());
+	if (!ctrl)
+		goto done;
+
+	for (e = 0; e < 20; e++) {
+		const char *events[] = {
+			"CTRL-EVENT-EAP-TLS-CERT-ERROR",
+			"CTRL-EVENT-DISCONNECTED",
+			"CTRL-EVENT-CONNECTED",
+			NULL
+		};
+		char buf[1024];
+		int res;
+
+		res = get_wpa_cli_events(dut, ctrl, events, buf, sizeof(buf));
+		if (res < 0) {
+			strlcpy(resp,
+				"ServerCertTrustResult,Accepted,Result,Association did not complete",
+				sizeof(resp));
+			goto done;
+		}
+		sigma_dut_print(dut, DUT_MSG_DEBUG, "Connection event: %s",
+				buf);
+
+		if (strstr(buf, "CTRL-EVENT-EAP-TLS-CERT-ERROR")) {
+			strlcpy(resp,
+				"ServerCertTrustResult,Accepted,Result,TLS server certitficate validation failed with updated profile",
+				sizeof(resp));
+			goto done;
+		}
+
+		if (strstr(buf, "CTRL-EVENT-DISCONNECTED")) {
+			num_disconnected++;
+
+			if (num_disconnected > 2) {
+				strlcpy(resp,
+					"ServerCertTrustResult,Accepted,Result,Connection failed",
+					sizeof(resp));
+				goto done;
+			}
+		}
+
+		if (strstr(buf, "CTRL-EVENT-CONNECTED")) {
+				strlcpy(resp,
+					"ServerCertTrustResult,Accepted,Result,Connected",
+					sizeof(resp));
+			break;
+		}
+	}
+
+done:
+	if (ctrl) {
+		wpa_ctrl_detach(ctrl);
+		wpa_ctrl_close(ctrl);
+	}
+
+	send_resp(dut, conn, SIGMA_COMPLETE, resp);
+	return STATUS_SENT;
+}
+
+
 static enum sigma_cmd_result cmd_dev_exec_action(struct sigma_dut *dut,
 						 struct sigma_conn *conn,
 						 struct sigma_cmd *cmd)
 {
 	const char *program = get_param(cmd, "Program");
+	const char *val;
 
 #ifdef MIRACAST
 	if (program && (strcasecmp(program, "WFD") == 0 ||
@@ -85,6 +203,10 @@
 	if (program && strcasecmp(program, "DPP") == 0)
 		return dpp_dev_exec_action(dut, conn, cmd);
 
+	val = get_param(cmd, "ServerCertTrust");
+	if (val)
+		return sta_server_cert_trust(dut, conn, val);
+
 	return ERROR_SEND_STATUS;
 }