diff mbox series

[9/9] dpp: use common state machine

Message ID 20240327151957.1446149-9-prestwoj@gmail.com (mailing list archive)
State New
Headers show
Series [1/9] dpp: prep for moving AAD within dpp_append_wrapped_data | expand

Checks

Context Check Description
tedd_an/pre-ci_am success Success
prestwoj/iwd-ci-gitlint success GitLint

Commit Message

James Prestwood March 27, 2024, 3:19 p.m. UTC
Begins using the common state machine within dpp-common.c. All
the message flow/handling is now taken care of by the SM. The
dpp.c module now only handles a specific set of functions
required by 802.11 encapsulation (and DBus):

 - Encapsulating and writing out frames for the SM.
 - Decapsulating and reading frames into the SM
 - Offchannel management; setting/unsetting the write handler as
   the offchannel work is ready or timed out.
 - DBus API.

The dpp.c module can now create its minimal dpp object on init and
the dpp_sm object lifetime follows the protocol. When DPP is
started dpp.c will set any options required for the configuration,
role, inititor vs responder, etc. DPP can then be started within
the SM, and any offchannel work should be queued. The basic flow
after that is quite simple:

 - If using offchannel the write handler must be set or unset as
   needed when frames can/can't be sent out. Offchannel frequencies
   are tracked by dpp.c, including presence/PKEX exchange frequency
   iteration.
 - Receved frames should be validated minimally: Prefix should be
   some type of DPP frame, and the source should be either a
   broadcast or from the expected peer (if known).
 - Received frames should be stripped of the mpdu header and
   category byte.
 - If the SM calls into the write handler the frame should be
   encapsulated with a mpdu header and category byte, then sent.
 - Upon receiving DPP events:
     DPP_EVENT_FAILED:
         The SM should be destroyed

     DPP_EVENT_PEER_ACCEPTED
         The MAC for the last message received should be tracked
         and only messages from the MAC should be passed to the SM
         going forward.

     DPP_EVENT_CHANNEL_SWITCH
         The channel should be switched to a new one, as communicated
         by the authentication request

     DPP_EVENT_PKEX_KEY_REQUESTED
         The PKEX key should be obtained and set into the SM using
         dpp_sm_set_pkex_key().

     DPP_EVENT_SUCCESS
         DPP completed. As an enrollee the configuration can now be
         obtained. The SM should be destroyed.
---
 src/dpp.c | 496 ++++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 353 insertions(+), 143 deletions(-)
diff mbox series

Patch

diff --git a/src/dpp.c b/src/dpp.c
index 31d0ef28..cd7facbf 100644
--- a/src/dpp.c
+++ b/src/dpp.c
@@ -54,6 +54,7 @@ 
 #include "src/handshake.h"
 #include "src/nl80211util.h"
 #include "src/knownnetworks.h"
+#include "src/dpp-common.h"
 
 #define DPP_FRAME_MAX_RETRIES 5
 #define DPP_FRAME_RETRY_TIMEOUT 1
@@ -67,20 +68,6 @@  static uint32_t unicast_watch;
 
 static uint8_t dpp_prefix[] = { 0x04, 0x09, 0x50, 0x6f, 0x9a, 0x1a, 0x01 };
 
-enum dpp_state {
-	DPP_STATE_NOTHING,
-	DPP_STATE_PRESENCE,
-	DPP_STATE_PKEX_EXCHANGE,
-	DPP_STATE_PKEX_COMMIT_REVEAL,
-	DPP_STATE_AUTHENTICATING,
-	DPP_STATE_CONFIGURING,
-};
-
-enum dpp_capability {
-	DPP_CAPABILITY_ENROLLEE = 0x01,
-	DPP_CAPABILITY_CONFIGURATOR = 0x02,
-};
-
 enum dpp_interface {
 	DPP_INTERFACE_UNBOUND,
 	DPP_INTERFACE_DPP,
@@ -95,6 +82,7 @@  struct pkex_agent {
 };
 
 struct dpp {
+	struct dpp_sm *sm;
 	struct netdev *netdev;
 	char *uri;
 	uint8_t role;
@@ -104,10 +92,10 @@  struct dpp {
 
 	uint64_t wdev_id;
 
+	/* Could probably do away with storing these here */
 	struct l_ecc_scalar *boot_private;
 	struct l_ecc_point *boot_public;
 
-	enum dpp_state state;
 	enum dpp_interface interface;
 
 	struct pkex_agent *agent;
@@ -121,18 +109,17 @@  struct dpp {
 	size_t freqs_idx;
 	uint32_t dwell;
 	uint32_t current_freq;
-	uint32_t new_freq;
 	struct scan_freq_set *presence_list;
 	uint32_t max_roc;
 
 	uint32_t offchannel_id;
 
 	uint8_t peer_addr[6];
+	bool peer_accepted;
 
 	/* Timeout of either auth/config protocol */
 	struct l_timeout *timeout;
 
-	struct dpp_configuration *config;
 	uint32_t connect_scan_id;
 	uint64_t frame_cookie;
 	uint8_t frame_retry;
@@ -140,19 +127,12 @@  struct dpp {
 	size_t frame_size;
 	struct l_timeout *retry_timeout;
 
-	struct l_dbus_message *pending;
-
 	struct l_idle *connect_idle;
 
-	/* PKEX-specific values */
-	char *pkex_id;
-	char *pkex_key;
-
 	uint32_t pkex_scan_id;
 
 	bool mcast_support : 1;
 	bool roc_started : 1;
-	bool channel_switch : 1;
 };
 
 static const char *dpp_role_to_string(enum dpp_capability role)
@@ -173,8 +153,7 @@  static bool dpp_pkex_get_started(struct l_dbus *dbus,
 				void *user_data)
 {
 	struct dpp *dpp = user_data;
-	bool started = (dpp->state != DPP_STATE_NOTHING &&
-			dpp->interface == DPP_INTERFACE_PKEX);
+	bool started = (dpp->sm && dpp->interface == DPP_INTERFACE_PKEX);
 
 	l_dbus_message_builder_append_basic(builder, 'b', &started);
 
@@ -189,8 +168,7 @@  static bool dpp_pkex_get_role(struct l_dbus *dbus,
 	struct dpp *dpp = user_data;
 	const char *role;
 
-	if (dpp->state == DPP_STATE_NOTHING ||
-				dpp->interface != DPP_INTERFACE_PKEX)
+	if (!dpp->sm || dpp->interface != DPP_INTERFACE_PKEX)
 		return false;
 
 	role = dpp_role_to_string(dpp->role);
@@ -207,8 +185,7 @@  static bool dpp_get_started(struct l_dbus *dbus,
 				void *user_data)
 {
 	struct dpp *dpp = user_data;
-	bool started = (dpp->state != DPP_STATE_NOTHING &&
-			dpp->interface == DPP_INTERFACE_DPP);
+	bool started = (dpp->sm && dpp->interface == DPP_INTERFACE_DPP);
 
 	l_dbus_message_builder_append_basic(builder, 'b', &started);
 
@@ -223,8 +200,7 @@  static bool dpp_get_role(struct l_dbus *dbus,
 	struct dpp *dpp = user_data;
 	const char *role;
 
-	if (dpp->state == DPP_STATE_NOTHING ||
-				dpp->interface != DPP_INTERFACE_DPP)
+	if (!dpp->sm || dpp->interface != DPP_INTERFACE_DPP)
 		return false;
 
 	role = dpp_role_to_string(dpp->role);
@@ -242,8 +218,7 @@  static bool dpp_get_uri(struct l_dbus *dbus,
 {
 	struct dpp *dpp = user_data;
 
-	if (dpp->state == DPP_STATE_NOTHING ||
-				dpp->interface != DPP_INTERFACE_DPP)
+	if (!dpp->sm || dpp->interface != DPP_INTERFACE_DPP)
 		return false;
 
 	l_dbus_message_builder_append_basic(builder, 's', dpp->uri);
@@ -380,11 +355,6 @@  static void dpp_reset(struct dpp *dpp)
 		dpp->timeout = NULL;
 	}
 
-	if (dpp->config) {
-		dpp_configuration_free(dpp->config);
-		dpp->config = NULL;
-	}
-
 	if (dpp->connect_scan_id) {
 		scan_cancel(dpp->wdev_id, dpp->connect_scan_id);
 		dpp->connect_scan_id = 0;
@@ -410,7 +380,6 @@  static void dpp_reset(struct dpp *dpp)
 		dpp->connect_idle = NULL;
 	}
 
-	dpp->new_freq = 0;
 	dpp->frame_retry = 0;
 	dpp->frame_cookie = 0;
 
@@ -419,6 +388,11 @@  static void dpp_reset(struct dpp *dpp)
 	dpp_property_changed_notify(dpp);
 
 	dpp->interface = DPP_INTERFACE_UNBOUND;
+
+	if (dpp->sm) {
+		dpp_sm_free(dpp->sm);
+		dpp->sm = NULL;
+	}
 }
 
 static void dpp_free(struct dpp *dpp)
@@ -510,7 +484,27 @@  static void dpp_frame_retry(struct dpp *dpp)
 	dpp->frame_pending = NULL;
 }
 
-static void dpp_write_config(struct dpp_configuration *config,
+static size_t dpp_build_mpdu_header(const uint8_t *src,
+					const uint8_t *dest, uint8_t *buf)
+{
+	struct mmpdu_header *hdr = (struct mmpdu_header *)buf;
+	uint8_t *body;
+
+	hdr->fc.protocol_version = 0;
+	hdr->fc.type = MPDU_TYPE_MANAGEMENT;
+	hdr->fc.subtype = MPDU_MANAGEMENT_SUBTYPE_ACTION;
+	memcpy(hdr->address_1, dest, 6);
+	memcpy(hdr->address_2, src, 6);
+	memcpy(hdr->address_3, broadcast, 6);
+
+	body = buf + mmpdu_header_len(hdr);
+	/* Category: Public */
+	*body++ = 0x04;
+
+	return body - buf;
+}
+
+static void dpp_write_config(const struct dpp_configuration *config,
 				struct network *network)
 {
 	_auto_(l_settings_free) struct l_settings *settings = l_settings_new();
@@ -555,9 +549,13 @@  static void dpp_start_connect(struct l_idle *idle, void *user_data)
 	struct scan_bss *bss;
 	struct network *network;
 	int ret;
+	const struct dpp_configuration *config;
 
-	network = station_network_find(station, dpp->config->ssid,
-					SECURITY_PSK);
+	config = dpp_sm_get_configuration(dpp->sm);
+	if (L_WARN_ON(!config))
+		return;
+
+	network = station_network_find(station, config->ssid, SECURITY_PSK);
 
 	dpp_reset(dpp);
 
@@ -618,7 +616,8 @@  static void dpp_known_network_watch(enum known_networks_event event,
 					void *user_data)
 {
 	struct dpp *dpp = user_data;
-
+	struct station *station = station_find(netdev_get_ifindex(dpp->netdev));
+	const struct dpp_configuration *config;
 	/*
 	 * Check the following
 	 *  - DPP is enrolling
@@ -628,16 +627,23 @@  static void dpp_known_network_watch(enum known_networks_event event,
 	 *    immediately modified after DPP synced it).
 	 *  - DPP didn't start a scan for the network.
 	 */
+	if (!dpp->sm)
+		return;
+
+	config = dpp_sm_get_configuration(dpp->sm);
+
 	if (dpp->role != DPP_CAPABILITY_ENROLLEE)
 		return;
-	if (!dpp->config)
+	if (!config)
 		return;
-	if (strcmp(info->ssid, dpp->config->ssid))
+	if (strcmp(info->ssid, config->ssid))
 		return;
 	if (dpp->connect_idle)
 		return;
 	if (dpp->connect_scan_id)
 		return;
+	if (!station || station_get_connected_network(station))
+		return;
 
 	switch (event) {
 	case KNOWN_NETWORKS_EVENT_ADDED:
@@ -663,54 +669,40 @@  static void dpp_handle_config_frame(const struct mmpdu_header *frame,
 					int rssi, void *user_data)
 {
 	struct dpp *dpp = user_data;
-	struct dpp_configuration *config = NULL;
-	struct station *station = station_find(netdev_get_ifindex(dpp->netdev));
-	struct network *network = NULL;
-	struct scan_bss *bss = NULL;
 
-	dpp_write_config(config, network);
+	if (!dpp->sm)
+		return;
 
-	offchannel_cancel(dpp->wdev_id, dpp->offchannel_id);
+	dpp_handle_rx(dpp->sm, body + 1, body_len - 1);
+}
 
-	if (network && bss) {
-		l_debug("delaying connect until settings are synced");
-		dpp->config = config;
-		return;
-	} else if (station) {
-		struct scan_parameters params = {0};
+static void dpp_tx_frame(const uint8_t *data, size_t len,
+				void *user_data)
+{
+	struct dpp *dpp = user_data;
+	struct iovec iov[2];
+	uint8_t hdr[36];
 
-		params.ssid = (void *) config->ssid;
-		params.ssid_len = config->ssid_len;
+	memset(hdr, 0, sizeof(hdr));
 
-		l_debug("Scanning for %s", config->ssid);
+	iov[0].iov_len = dpp_build_mpdu_header(netdev_get_address(dpp->netdev),
+						dpp->peer_addr, hdr);
+	iov[0].iov_base = hdr;
 
-		dpp->connect_scan_id = scan_active_full(dpp->wdev_id, &params,
-						dpp_scan_triggered,
-						dpp_scan_results, dpp,
-						dpp_scan_destroy);
-		if (dpp->connect_scan_id) {
-			dpp->config = config;
-			return;
-		}
-	}
+	iov[1].iov_base = (void *)data;
+	iov[1].iov_len = len;
 
+	dpp_send_frame(dpp, iov, 2, dpp->current_freq);
 }
 
 static void dpp_roc_started(void *user_data)
 {
 	struct dpp *dpp = user_data;
 
-	/*
-	 * - If a configurator, nothing to do but wait for a request
-	 *   (unless multicast frame registration is unsupported in which case
-	 *   send an authenticate request now)
-	 * - If in the presence state continue sending announcements.
-	 * - If authenticating, and this is a result of a channel switch send
-	 *   the authenticate response now.
-	 */
-
 	dpp->roc_started = true;
 
+	dpp_sm_set_write_handler(dpp->sm, dpp_tx_frame);
+
 	/*
 	 * The retry timer indicates a frame was not acked in which case we
 	 * should not change any state or send any frames until that expires.
@@ -733,6 +725,8 @@  static void dpp_offchannel_timeout(int error, void *user_data)
 	dpp->offchannel_id = 0;
 	dpp->roc_started = false;
 
+	dpp_sm_set_write_handler(dpp->sm, NULL);
+
 	/*
 	 * If cancelled this is likely due to netdev going down or from Stop().
 	 * Otherwise there was some other problem which is probably not
@@ -790,6 +784,8 @@  static void dpp_start_offchannel(struct dpp *dpp, uint32_t freq)
 	if (dpp->offchannel_id)
 		offchannel_cancel(dpp->wdev_id, dpp->offchannel_id);
 
+	l_debug("starting offchannel on frequency %u, dwell=%u", freq, dpp->dwell);
+
 	dpp->offchannel_id = id;
 }
 
@@ -797,23 +793,16 @@  static void dpp_handle_frame(struct dpp *dpp,
 				const struct mmpdu_header *frame,
 				const void *body, size_t body_len)
 {
-	const uint8_t *ptr;
+	if (!dpp->sm)
+		return;
 
-	/*
-	 * Both handlers offset by 8 bytes to reach the beginning of the DPP
-	 * attributes. Easier checking this in one place, which also covers the
-	 * frame type byte.
-	 */
-	if (body_len < 8)
+	/* Frame from a different device after DPP has already started */
+	if (dpp->peer_accepted && memcmp(dpp->peer_addr, frame->address_2, 6))
 		return;
 
-	ptr = body + 7;
+	memcpy(dpp->peer_addr, frame->address_2, 6);
 
-	switch (*ptr) {
-	default:
-		l_debug("Unhandled DPP frame %u", *ptr);
-		break;
-	}
+	dpp_handle_rx(dpp->sm, body + 1, body_len - 1);
 }
 
 static bool match_wdev(const void *a, const void *b)
@@ -851,6 +840,7 @@  static void dpp_mlme_notify(struct l_genl_msg *msg, void *user_data)
 	bool ack = false;
 	struct iovec iov;
 	uint8_t cmd = l_genl_msg_get_command(msg);
+	enum dpp_state state;
 
 	if (cmd != NL80211_CMD_FRAME_TX_STATUS)
 		return;
@@ -863,16 +853,17 @@  static void dpp_mlme_notify(struct l_genl_msg *msg, void *user_data)
 		return;
 
 	dpp = l_queue_find(dpp_list, match_wdev, &wdev_id);
-	if (!dpp)
+	if (!dpp || !dpp->sm)
 		return;
 
+	state = dpp_sm_get_state(dpp->sm);
+
 	/*
 	 * Don't retransmit for presence or PKEX exchange if an enrollee, both
 	 * are broadcast frames which don't expect an ack.
 	 */
-	if (dpp->state == DPP_STATE_NOTHING ||
-			dpp->state == DPP_STATE_PRESENCE ||
-			(dpp->state == DPP_STATE_PKEX_EXCHANGE &&
+	if (state == DPP_STATE_PRESENCE ||
+			(state == DPP_STATE_PKEX_EXCHANGE &&
 			dpp->role == DPP_CAPABILITY_ENROLLEE))
 		return;
 
@@ -886,20 +877,6 @@  static void dpp_mlme_notify(struct l_genl_msg *msg, void *user_data)
 	if (!ack)
 		goto retransmit;
 
-	/*
-	 * Special handling for a channel transition when acting as a
-	 * configurator. The auth request was sent offchannel so we need to
-	 * wait for the ACK before going back to the connected channel.
-	 */
-	if (dpp->channel_switch) {
-		if (dpp->offchannel_id) {
-			offchannel_cancel(dpp->wdev_id, dpp->offchannel_id);
-			dpp->offchannel_id = 0;
-		}
-
-		dpp->channel_switch = false;
-	}
-
 	return;
 
 retransmit:
@@ -1035,7 +1012,7 @@  static void dpp_station_state_watch(enum station_state state, void *user_data)
 {
 	struct dpp *dpp = user_data;
 
-	if (dpp->state == DPP_STATE_NOTHING)
+	if (!dpp->sm)
 		return;
 
 	switch (state) {
@@ -1089,17 +1066,20 @@  static void dpp_create(struct netdev *netdev)
 	uint8_t dpp_conf_request_prefix[] = { 0x04, 0x0a };
 	uint64_t wdev_id = netdev_get_wdev_id(netdev);
 	struct station *station = station_find(netdev_get_ifindex(netdev));
+	const struct l_ecc_curve *curve = l_ecc_curve_from_ike_group(19);
 
 	dpp->netdev = netdev;
-	dpp->state = DPP_STATE_NOTHING;
 	dpp->interface = DPP_INTERFACE_UNBOUND;
 	dpp->wdev_id = wdev_id;
-
 	dpp->max_roc = wiphy_get_max_roc_duration(wiphy_find_by_wdev(wdev_id));
 	dpp->mcast_support = wiphy_has_ext_feature(
 				wiphy_find_by_wdev(dpp->wdev_id),
 				NL80211_EXT_FEATURE_MULTICAST_REGISTRATIONS);
 
+	/* TODO: Support a user-provided key pair */
+	l_ecdh_generate_key_pair(curve, &dpp->boot_private,
+					&dpp->boot_public);
+
 	l_dbus_object_add_interface(dbus, netdev_get_path(netdev),
 					IWD_DPP_INTERFACE, dpp);
 	l_dbus_object_add_interface(dbus, netdev_get_path(netdev),
@@ -1212,6 +1192,175 @@  static void dpp_start_presence(struct dpp *dpp, uint32_t *limit_freqs,
 	dpp_start_offchannel(dpp, dpp->current_freq);
 }
 
+static void dpp_event_success(struct dpp *dpp)
+{
+	struct station *station = station_find(netdev_get_ifindex(dpp->netdev));
+	struct network *network = NULL;
+	struct scan_bss *bss = NULL;
+	const struct dpp_configuration *config;
+
+	config = dpp_sm_get_configuration(dpp->sm);
+	if (L_WARN_ON(!config))
+		goto reset;
+
+	/*
+	 * We should have a station device, but if not DPP can write the
+	 * credentials out and be done
+	 */
+	if (station) {
+		network = station_network_find(station, config->ssid,
+						SECURITY_PSK);
+		if (network)
+			bss = network_bss_select(network, true);
+	}
+
+	dpp_write_config(config, network);
+
+	offchannel_cancel(dpp->wdev_id, dpp->offchannel_id);
+	dpp->offchannel_id = 0;
+
+	if (network && bss) {
+		l_debug("delaying connect until settings are synced");
+		return;
+	} else if (station) {
+		struct scan_parameters params = {0};
+
+		params.ssid = (void *) config->ssid;
+		params.ssid_len = config->ssid_len;
+
+		l_debug("Scanning for %s", config->ssid);
+
+		dpp->connect_scan_id = scan_active_full(dpp->wdev_id, &params,
+						dpp_scan_triggered,
+						dpp_scan_results, dpp,
+						dpp_scan_destroy);
+		if (dpp->connect_scan_id)
+			return;
+	}
+
+reset:
+	dpp_reset(dpp);
+}
+
+static void dpp_event_channel_switch(struct dpp *dpp,
+					const uint8_t *attr)
+{
+	uint32_t freq;
+
+	freq = oci_to_frequency(attr[0], attr[1]);
+
+	if (freq == dpp->current_freq)
+		return;
+
+	dpp->current_freq = freq;
+
+	dpp_start_offchannel(dpp, dpp->current_freq);
+
+	/*
+	 * The common code will attempt to write after this, so ensure that
+	 * gets delayed until we are on the right channel
+	 */
+	dpp_sm_set_write_handler(dpp->sm, NULL);
+}
+
+static void dpp_pkex_agent_reply(struct l_dbus_message *message,
+					void *user_data)
+{
+	struct dpp *dpp = user_data;
+	const char *error, *text;
+	const char *code;
+
+	dpp->agent->pending_id = 0;
+
+	l_debug("SharedCodeAgent %s path %s replied", dpp->agent->owner,
+			dpp->agent->path);
+
+	if (l_dbus_message_get_error(message, &error, &text)) {
+		l_error("RequestSharedCode() returned %s(\"%s\")",
+				error, text);
+		return;
+	}
+
+	if (!l_dbus_message_get_arguments(message, "s", &code)) {
+		l_debug("Invalid arguments, check SharedCodeAgent!");
+		return;
+	}
+
+	dpp_sm_set_pkex_key(dpp->sm, code);
+}
+
+static bool dpp_event_pkex_key_requested(struct dpp *dpp,
+					const char *identifier)
+{
+	struct l_dbus_message *msg;
+
+	if (!dpp->agent)
+		return false;
+
+	if (L_WARN_ON(dpp->agent->pending_id))
+		return false;
+
+	msg = l_dbus_message_new_method_call(dbus_get_bus(),
+						dpp->agent->owner,
+						dpp->agent->path,
+						IWD_SHARED_CODE_AGENT_INTERFACE,
+						"RequestSharedCode");
+	l_dbus_message_set_arguments(msg, "s", identifier);
+
+	dpp->agent->pending_id = l_dbus_send_with_reply(dbus_get_bus(),
+							msg,
+							dpp_pkex_agent_reply,
+							dpp, NULL);
+	return dpp->agent->pending_id != 0;
+}
+
+static void dpp_event_peer_accepted(struct dpp *dpp)
+{
+	dpp->peer_accepted = true;
+
+	if (dpp_sm_get_state(dpp->sm) != DPP_STATE_PKEX_EXCHANGE)
+		return;
+
+	dpp_sm_set_pkex_peer_mac(dpp->sm, dpp->peer_addr);
+
+	/*
+	 * PKEX dictates a 200ms timeout waiting for the exchange
+	 * response. After this there is no time requirement for the
+	 * remainder of the protocol. Increase the dwell time so we
+	 * have the best chance of receiving frames since we will now
+	 * remain on a single frequency.
+	 */
+	if (dpp->role == DPP_CAPABILITY_ENROLLEE)
+		dpp->dwell = (dpp->max_roc < 2000) ? dpp->max_roc : 2000;
+}
+
+static void dpp_event(enum dpp_event event, const void *event_data,
+			void *user_data)
+{
+	struct dpp *dpp = user_data;
+
+	switch (event) {
+	case DPP_EVENT_PEER_ACCEPTED:
+		dpp_event_peer_accepted(dpp);
+		break;
+	case DPP_EVENT_CHANNEL_SWITCH:
+		dpp_event_channel_switch(dpp, event_data);
+		break;
+	case DPP_EVENT_PKEX_KEY_REQUESTED:
+		dpp_event_pkex_key_requested(dpp, event_data);
+		break;
+	case DPP_EVENT_FAILED:
+		dpp_reset(dpp);
+		break;
+	case DPP_EVENT_SUCCESS:
+		if (dpp->role == DPP_CAPABILITY_ENROLLEE)
+			dpp_event_success(dpp);
+		else
+			dpp_reset(dpp);
+		break;
+	}
+}
+
 static struct l_dbus_message *dpp_dbus_start_enrollee(struct l_dbus *dbus,
 						struct l_dbus_message *message,
 						void *user_data)
@@ -1219,9 +1368,11 @@  static struct l_dbus_message *dpp_dbus_start_enrollee(struct l_dbus *dbus,
 	struct dpp *dpp = user_data;
 	uint32_t freq = band_channel_to_freq(6, BAND_FREQ_2_4_GHZ);
 	struct station *station = station_find(netdev_get_ifindex(dpp->netdev));
+	struct l_dbus_message *reply;
+	const uint8_t *own_asn1;
+	size_t own_asn1_len;
 
-	if (dpp->state != DPP_STATE_NOTHING ||
-				dpp->interface != DPP_INTERFACE_UNBOUND)
+	if (dpp->sm || dpp->interface != DPP_INTERFACE_UNBOUND)
 		return dbus_error_busy(message);
 
 	/*
@@ -1234,14 +1385,23 @@  static struct l_dbus_message *dpp_dbus_start_enrollee(struct l_dbus *dbus,
 	} else if (!station)
 		l_debug("No station device, continuing anyways...");
 
-	dpp->state = DPP_STATE_PRESENCE;
+	dpp->sm = dpp_sm_new(dpp_event, dpp->boot_public,
+				dpp->boot_private, dpp);
+
+	dpp_sm_set_role(dpp->sm, DPP_CAPABILITY_ENROLLEE);
+	dpp_sm_start_responder(dpp->sm);
+
+	own_asn1 = dpp_sm_get_own_asn1(dpp->sm, &own_asn1_len);
+
+	dpp->uri = dpp_generate_uri(own_asn1, own_asn1_len, 2,
+					netdev_get_address(dpp->netdev), &freq,
+					1, NULL, NULL);
+
 	dpp->role = DPP_CAPABILITY_ENROLLEE;
 	dpp->interface = DPP_INTERFACE_DPP;
 
 	l_debug("DPP Start Enrollee: %s", dpp->uri);
 
-	dpp->pending = l_dbus_message_ref(message);
-
 	/*
 	 * Going off spec here. Select a single channel to send presence
 	 * announcements on. This will be advertised in the URI. The full
@@ -1251,7 +1411,13 @@  static struct l_dbus_message *dpp_dbus_start_enrollee(struct l_dbus *dbus,
 
 	dpp_property_changed_notify(dpp);
 
-	return NULL;
+	memcpy(dpp->peer_addr, broadcast, 6);
+
+	reply = l_dbus_message_new_method_return(message);
+
+	l_dbus_message_set_arguments(reply, "s", dpp->uri);
+
+	return reply;
 }
 
 /*
@@ -1284,12 +1450,16 @@  static bool dpp_configurator_start_presence(struct dpp *dpp, const char *uri)
 		return false;
 	}
 
-	if (!l_memeqzero(info->mac, 6))
+	if (!l_memeqzero(info->mac, 6)) {
 		memcpy(dpp->peer_addr, info->mac, 6);
+		/* Set now so we restrict to only frames from this MAC */
+		dpp->peer_accepted = true;
+	}
 
 	if (info->freqs)
 		freqs = scan_freq_set_to_fixed_array(info->freqs, &freqs_len);
 
+	dpp_sm_set_peer_bootstrap(dpp->sm, info->boot_public);
 	dpp_free_uri_info(info);
 
 	dpp_start_presence(dpp, freqs, freqs_len);
@@ -1311,6 +1481,9 @@  static struct l_dbus_message *dpp_start_configurator_common(
 	struct l_settings *settings;
 	struct handshake_state *hs = netdev_get_handshake(dpp->netdev);
 	const char *uri;
+	struct dpp_configuration *config;
+	const uint8_t *own_asn1;
+	size_t own_asn1_len;
 
 	/*
 	 * For now limit the configurator to only configuring enrollees to the
@@ -1331,11 +1504,11 @@  static struct l_dbus_message *dpp_start_configurator_common(
 	if (network_get_security(network) != SECURITY_PSK)
 		return dbus_error_not_supported(message);
 
-	if (dpp->state != DPP_STATE_NOTHING ||
-				dpp->interface != DPP_INTERFACE_UNBOUND)
+	if (dpp->sm || dpp->interface != DPP_INTERFACE_UNBOUND)
 		return dbus_error_busy(message);
 
-	dpp->state = DPP_STATE_PRESENCE;
+	dpp->sm = dpp_sm_new(dpp_event, dpp->boot_public,
+				dpp->boot_private, dpp);
 
 	if (!responder) {
 		if (!l_dbus_message_get_arguments(message, "s", &uri))
@@ -1345,26 +1518,47 @@  static struct l_dbus_message *dpp_start_configurator_common(
 			return dbus_error_invalid_args(message);
 
 		if (!dpp->mcast_support)
-			dpp->state = DPP_STATE_AUTHENTICATING;
-
-		dpp->new_freq = bss->frequency;
+			dpp_sm_set_skip_presence(dpp->sm, true);
 	} else
 		dpp->current_freq = bss->frequency;
 
-	dpp->role = DPP_CAPABILITY_CONFIGURATOR;
-	dpp->interface = DPP_INTERFACE_DPP;
-	dpp->config = dpp_configuration_new(settings,
-						network_get_ssid(network),
+	config = dpp_configuration_new(settings, network_get_ssid(network),
 						hs->akm_suite);
 
+	dpp_sm_set_configuration(dpp->sm, config);
+	dpp_sm_set_role(dpp->sm, DPP_CAPABILITY_CONFIGURATOR);
+
+	own_asn1 = dpp_sm_get_own_asn1(dpp->sm, &own_asn1_len);
+
+	dpp->uri = dpp_generate_uri(own_asn1, own_asn1_len, 2,
+					netdev_get_address(dpp->netdev),
+					&bss->frequency, 1, NULL, NULL);
+	dpp->interface = DPP_INTERFACE_DPP;
+
+	dpp->dwell = (dpp->max_roc < 2000) ? dpp->max_roc : 2000;
+
 	dpp_property_changed_notify(dpp);
 
+	dpp->role = DPP_CAPABILITY_CONFIGURATOR;
+
+	memcpy(dpp->peer_addr, broadcast, 6);
+
 	l_debug("DPP Start Configurator: %s", dpp->uri);
 
 	reply = l_dbus_message_new_method_return(message);
 
 	l_dbus_message_set_arguments(reply, "s", dpp->uri);
 
+	/*
+	 * Configurators acting as responders are always on channel so
+	 * the writes can be done at any point.
+	 */
+	if (responder) {
+		dpp_sm_set_write_handler(dpp->sm, dpp_tx_frame);
+		dpp_sm_start_responder(dpp->sm);
+	} else
+		dpp_sm_start_initiator(dpp->sm);
+
 	return reply;
 }
 
@@ -1480,10 +1674,12 @@  static bool dpp_pkex_scan_notify(int err, struct l_queue *bss_list,
 start:
 	dpp->current_freq = dpp->freqs[0];
 
-	l_debug("PKEX start enrollee (id=%s)", dpp->pkex_id ?: "unset");
+	l_debug("PKEX start enrollee");
 
 	dpp_start_offchannel(dpp, dpp->current_freq);
 
+	dpp_sm_pkex_start_initiator(dpp->sm);
+
 	return false;
 
 failed:
@@ -1503,14 +1699,20 @@  static bool dpp_start_pkex_enrollee(struct dpp *dpp, const char *key,
 {
 	_auto_(l_ecc_point_free) struct l_ecc_point *qi = NULL;
 
+	dpp->sm = dpp_sm_new(dpp_event, dpp->boot_public,
+				dpp->boot_private, dpp);
+
 	if (identifier)
-		dpp->pkex_id = l_strdup(identifier);
+		dpp_sm_set_pkex_identifier(dpp->sm, identifier);
+
+	dpp_sm_set_pkex_key(dpp->sm, key);
+	dpp_sm_set_pkex_own_mac(dpp->sm, netdev_get_address(dpp->netdev));
+	dpp_sm_set_role(dpp->sm, DPP_CAPABILITY_ENROLLEE);
 
-	dpp->pkex_key = l_strdup(key);
 	memcpy(dpp->peer_addr, broadcast, 6);
 	dpp->role = DPP_CAPABILITY_ENROLLEE;
-	dpp->state = DPP_STATE_PKEX_EXCHANGE;
 	dpp->interface = DPP_INTERFACE_PKEX;
+
 	/*
 	 * In theory a driver could support a lesser duration than 200ms. This
 	 * complicates things since we would need to tack on additional
@@ -1596,8 +1798,7 @@  static struct l_dbus_message *dpp_dbus_pkex_start_enrollee(struct l_dbus *dbus,
 
 	l_debug("");
 
-	if (dpp->state != DPP_STATE_NOTHING ||
-				dpp->interface != DPP_INTERFACE_UNBOUND)
+	if (dpp->sm || dpp->interface != DPP_INTERFACE_UNBOUND)
 		return dbus_error_busy(message);
 
 	if (station && station_get_connected_network(station))
@@ -1651,9 +1852,9 @@  static struct l_dbus_message *dpp_start_pkex_configurator(struct dpp *dpp,
 	struct network *network = station_get_connected_network(station);
 	struct scan_bss *bss = station_get_connected_bss(station);
 	const struct l_settings *settings;
+	struct dpp_configuration *config;
 
-	if (dpp->state != DPP_STATE_NOTHING ||
-				dpp->interface != DPP_INTERFACE_UNBOUND)
+	if (dpp->sm || dpp->interface != DPP_INTERFACE_UNBOUND)
 		return dbus_error_busy(message);
 
 	if (!dpp->mcast_support) {
@@ -1671,30 +1872,39 @@  static struct l_dbus_message *dpp_start_pkex_configurator(struct dpp *dpp,
 		return dbus_error_not_configured(message);
 	}
 
+	dpp->sm = dpp_sm_new(dpp_event, dpp->boot_public,
+				dpp->boot_private, dpp);
+
 	if (identifier)
-		dpp->pkex_id = l_strdup(identifier);
+		dpp_sm_set_pkex_identifier(dpp->sm, identifier);
 
 	if (key)
-		dpp->pkex_key = l_strdup(key);
+		dpp_sm_set_pkex_key(dpp->sm, key);
 
 	if (agent_path)
 		dpp_create_agent(dpp, agent_path, message);
 
 	dpp->role = DPP_CAPABILITY_CONFIGURATOR;
-	dpp->state = DPP_STATE_PKEX_EXCHANGE;
 	dpp->interface = DPP_INTERFACE_PKEX;
 	dpp->current_freq = bss->frequency;
-	dpp->config = dpp_configuration_new(network_get_settings(network),
+	config = dpp_configuration_new(network_get_settings(network),
 						network_get_ssid(network),
 						hs->akm_suite);
 
+	dpp_sm_set_role(dpp->sm, DPP_CAPABILITY_CONFIGURATOR);
+	dpp_sm_set_configuration(dpp->sm, config);
+	dpp_sm_set_pkex_own_mac(dpp->sm, netdev_get_address(dpp->netdev));
+	dpp_sm_set_write_handler(dpp->sm, dpp_tx_frame);
+
 	dpp_property_changed_notify(dpp);
 
-	if (dpp->pkex_key)
+	if (key)
 		l_debug("Starting PKEX configurator for single enrollee");
 	else
 		l_debug("Starting PKEX configurator with agent");
 
+	dpp_sm_pkex_start_responder(dpp->sm);
+
 	return l_dbus_message_new_method_return(message);
 }